nRF5340学习笔记(二)设备树的初步学习 |
您所在的位置:网站首页 › visio 序列图 双冒号 › nRF5340学习笔记(二)设备树的初步学习 |
一、参考资料与链接 前片文章中有一个博客链接,讲述Zephyr设备树与驱动模型,对应在B站,也有官方的详解视频。视频也大致讲述了Device Tree(设备树)的语法与结构、如何用DeviceTree配置硬件信息、以及C代码访问DeviceTree和Zephyr Driver实现方式。视频链接如下,建议详细看完博客后再看一遍视频,有些不懂得地方就有思路了。 视频连接:详解Zephyr设备树与设备驱动模型 另外我们也可以通过nordic官网找到nRF Connect SDK的专栏资料。其对应的Zephyr Project栏目下,就有一章专门讲述DeviceTree的文档指导。文档链接地址如下。 https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/build/dts/index.html 下面,我们根据博客内容,参考北欧开发者学院的视频指导,学习设备树的内容,主要是其API、文档结构与驱动模型。以此为基础,在上一个点灯工程内,新增一个button的应用。 二、NCS下板级配置 2.1:Kconfig与DeviceTree回到上一篇文章里中,打开文中《开发你的第一个nRF Connect SDK(NCS)/Zephyr应用程序》的链接,跳转到第4章节内容:--nRF Connect SDK项目配置和选项--。 该章节内容讲述到,NCS中主要包含两种配置项:Kconfig和DeviceTree,其中。 Kconfig主要负责软件配置,生成头文件为autoconf.h DeviceTree负责硬件配置,生成头文件为devicetree_generated.h 无论是autoconf.h还是devicetree_generated.h,我们在生成的点灯工程中打开这两个文件。如下图所示。 工程目录下文件配置定义在NCS中,autoconf.h/devicetree_generated.h是由Python脚本自动生成的,所以作为使用者是无法直接修改的。我们只能去修改这两个头文件的输入。 2.2:Kconfig生成文件autoconf.h文件生成的流程图如下图所示 autoconf.h生成流程图autoconf.h还是有许许多多的Kconfig文件生成的(注:其实Kconfig来源于Linux系统,NCS或者Zephyr对其进行了继承和定制),每个模块都有自己的Kconfig文件。如下图,我们可以在文档中给出地址,找到各个模块Kconfig所存放的文件夹地址。 Kconfig source除了系统模块定义好的Kconfig,我们本身项目模块也可以自定义Kconfig文件,模仿现有文件,在文本编辑器里编写。 因为Kconfig文件都是SDK中提供好的模块,每个模块都有预定的一个默认值,如果想修改这个默认值,像在source里面一个一个找到地址,再去修改吗岂不是非常繁琐。为此,NCS引入prj.conf文件。我们打开点灯程序中的prj。 prj.conf文件因为只是使用了一个GPIO口,所以配置内容比较稀疏。我们通过pri.conf,也可以修改默认的Kconfig选项,而且这个是永久更新,且只适应于本项目。 Kconfig与我们设置的prj.conf结合后,会先生成一个.conf文件,在生成autoconfig.h。 .config文件.config文件格式更加接近Kconfig和prj.conf,相当于一个中间桥梁。我们在VScode的图形插件里面修改Kconfig其实操作对象就是.config文件,.config是一个临时文件,编译系统默认会以它为基准生成autoconfig.h。所以一旦Kconfig或者prj.conf更新了,必须重新编译以生成的autoconfig.h文件。 2.3 :Device Tree生成文件devicetree_generated.h(老版本为devicetree_unfixed.h)生成流程如下图。 devicetree_generated.h生成流程图DeviceTree是Linux里面的概念,在DeviceTree中,每一种硬件都可以用DeviceTree语言进行描述,无论是芯片,元器件,包括内含另一种板子。DeviceTree使用树形结构,按照层级关系描述板子中包含的组件。 每一块板子都会定义一个dts文件。我们使用的开发板是nRF5340DK,例程我们跑在应用核的安全空间,回到我们下载SDK中,可以找到对应的dts文件。 SDK中5340的.dts除了板级的.dts,devicetree_generated还有一个输入来源,即本项目的硬件配置文件,overlay文件。我们如果想要求改自己项目下的配置,比如开一个uart、iic、spi,只需要在该项目下定义一个.overlay文件即可。 与prj.conf一样,通过.overlay修改的板子的配置,是永久的,且只适应于本项目。随后通过.dts和.overlay结合生成一个zephyr.dts文件,最后在生成devicetree_generated.h。 zephyr.dts三、设备树的基础知识第二节我们讲述了NCS下,最核心的配置文件的两个功能,Kconfig和DeviceTree。Kconfig有点类似我们nRF52 SDK中的config.h,用于软件配置是否开启外设驱动。而devicetree是处理硬件部分,为外设的引导配置,设置外设中断,属性等功能。 进一步了解devicetree,首先其是一个树状结构,树状的结构层次决定方式如下 首先看总线的主从关系、其次看硬件的包含关系 3.1 DeviceTree的语法设备树是一种树状结构。此树的用户可读文本格式为.dts (1)DTS基本示例 1. /dts -v1/; 指明了设备树语法的版本 2.设备树具有唯一的根节点,节点之间的包含关系使用大括号来确定 3.节点名称写在大括号之前,节点的属性写在大括号内;属性是键值对(Key-Value pair)的形式。 4.可以给节点写一个标签(label),便于在其他地方引用 ex:指明一个节点,需要完整的绝对路径, 例如:/a-node/a-sub-bode ex:也可以直接使用标签来指明, 例如:subnode_label (2)DeviceTree中节点名称 name@adress .name:必须以字母开头。长度1-31字节,允许数字、大小写字母、英文逗号、小数点、加减号、下划线 @address:节点若果有reg属性,则address必须和reg属性的第一个寄存器地址值相等。可以理解为总线上的地址。如果没有reg属性,则@address必须省略。 address是十六进制 ex: 注意1:英文逗号、小数点、加减号都可以使用,但是在C语言访问需要统一下划线 (3)DeviceTree中属性类型 属性类型表注意说明 phandle:是用来指向其他节点的属性,形成数组即phandles。 phandle-array:就是一个节点,后面可以带任何信息 (4)DeviceTree文件引用 1. dts可以引用其他dts或dtsi,这样板卡级dts可以引用芯片级dits,减少编写工作量 点灯工程引用了应用核common与芯片的dtsi2.dts也可以引用C语言头文件,从而可以使用里面的枚举值和宏定义 3.2 用DeviceTree配置硬件信息有3.1我们可以了解到,DeviceTree本身的结构和语法比较简单,只是规定一种形式,和硬件的配置没有任何关系。DeviceTree的基本单元是节点(node),节点具有一个名称和多条属性。可以给节点增加标签(label),来便于引用这个节点。 板卡级别.dts文件可以引用芯片及dtsi,也可以引用.h头文件,从而定义枚举和宏定义。如果用户想自己修改配置,可以在工程里通过写.overlay的方式覆写。 至于导出文件,第一章节有提到,Zephyr Build System会通过python和各种脚本,会合并所有的dts以及overlay,最终生成zephyr.dts,并导出devicetree_generated.h头文件用于编译。 对于DeviceTree配置硬件,需要了解一些常见属性 (1)reg、#address-cells与#size-cells reg属性:代表此节点在总线上占用的地址和范围。是由多对(address,length)组合而成的。 #address-cells和#size-cells则表示这个总线上的节点的reg属性里,每个address和size要占用多少个uint32单元。如下代码所示 也就是说,每个节点的address-cell和size-cell是用来管理子节点地址的寄存器的。 (2)ranges 当一个节点定义了ranges属性,那么他的子节点就可以使用相对地址,而非绝对地址。如下图所示,peripheral基地址为0x40000000。 ADC的地址从0xe000开始,是一个相对地址 ADC在RAM地址空间绝对地址为0x4000e000 ranges的属性格式为
子空间首地址为0时,子节点的地址就是相对地址 这三个元素要占用几个uint32单元,由图中同色*-cells决定 带有rangs子节点定义*(3)status status是用来制定是否启用一个设备(节点),根据DeviceTree Spec有几个选项,但是我们实际在Zephyr中基本只会用到以下两个 "okay" : 设备是可以操作 “disable” :设备目前是不可操作的 ***(4)compatible compatible用来说明此节点设备的兼容性,他的值是一个字符串或一个字符串数组。Zephyr构建系统就是用它为每个节点找到合适的驱动程序 compatible的每个值通常命名规则:vendor,device (供应商,供应商产品)如下 但是也不做强制要求,可以用户自定义如下 如果compatible有多个值,zephyr会按顺序寻找驱动 3.3 域DomainDeviceTree是基于总线地址的层次结构(树状结构) 实际硬件是网状结构,所以DeviceTree如果需要描述实际情况,在本身基于地址的数之外,逻辑上还有一些其他数也可以用DeviceTree描述,eg:GPIO数、中断树、ADC树等等。 这种附加在DeviceTree上,逻辑上产生的树称之为域(Domain) 每个域都有一个自己的根节点,该根节点称之为控制器(controller),控制器控制了整个域的相关硬件 (1)域的控制器和子节点 控制器节点通常通常会有一个布尔类型属性*-controller,来表示自己是某个域的控制器 而域中的子节点,就可以使用phandle-array类型的属性,来说明自己属于那个域。此属性的第一个值指向控制器的句柄。后续的值是此节点在这个域中的配置,这条配置称为specifier 控制器节点中会有一个#*-cells属性来指明specifier的大小,需要占用多少个uint32单元 如下代码段 gpios = specifier中断域、adc域、pwm域不做解释,后续用到了在把代码贴出来详细注释 初学者(我)只需要记住,【specifier是用来写配置的】即可 (2):Device Binding DeviceTree规则:Device Binding binding文件是yaml格式文件,由多组键值对组成。每个值可以是: 纯量:单个不可分割的值 对象:把键值对当成值 数组:一组同类型的值 简易语法: 键、值之间用冒号+空格分隔 yaml的层级关系只看缩进(类似python),相同层级缩进必须相同 数组元素可以是纯量、对象。对象的成员也可以有数组(和json非常相似) Device Binding作用: binding和device tree中的节点,通过conmpatible属性实现联动。 device tree中节点的属性,必须严格按照binding文件中的要求。 不同厂商都会针对每个模块写好binding文件 作为用户也可以自定binding文件 (3):特殊节点 /chosen:zephyr kernel选择特定设备 /aliases:给节点起一个别名,类似label。区别是label任是节点,aliases只是属性名 /pinctrl:直属根节点,用于管理数字IO的附庸 /zephyr,user:方便用户开的节点,可以直接在里面写spcifier、配置项等,免去自定义device和写binding的麻烦 3.4 总结DeviceTree本身语法只是提供了一个基于总线主从关系的树形层次结构,此外每个节点可以用属性来存储信息。语法本身并没有规定硬件要如何描述 DeviceTree中的一些常见属性,补充了这方面的空缺 reg、rangs、#address-cells、#size-cells这四个属性描述了总线上的地址分配 status描述了设备是否使能 compatible属性描述了设备的兼容 在DeviceTree中,除本身的树形结构以外,还具有一些逻辑上的树形结构,称为域。域具有控制器和设备节点,控制器用来实现域的功能的硬件外设,设备节点为了开发方便解耦而进行的一种抽象 实际限制device tree中属性如何写的是device binding文件。binding文件是芯片厂商提供的。有了binding文件,就可以在VS code中实现自动的检查和补全。 zephyr中会有一些特殊的虚拟节点为开发提供便利(eg:chosen、aliase、pinctrl、zephyr user) 四、在C代码中访问Device Tree 4.1 获取节点IDDeviceTree最终会用来生成devicetree_generated.h头文件,包含了DeviceTree中的所有信息,在C文件中访问这些信息,需要使用一套宏函数来将其读出。 首先操作的C文件中包含其头文件 DeviceTree的一切信息都包含在属性之中。获取属性先获取节点ID做句柄。获取节点ID方式很多eg: 其他方式参考:https://docs.zephyrproject.org/latest/build/dts/api-usage.html 4.2 获取属性利用DeviceTree API,输入节点id和属性名称,就可以获得属性 检查属性是否存在: node id 和小写、下划线命名的属性名称 获取普通属性:DT_PROP(node_id) 获取reg属性: 1.获取reg blocks数量:DT_NUM_REGS(node_id) 2.只有1个block,读取地址和长度如下 DT_REG_ADDR(node_id) DT_REG_SIZE(node_id) 3.有多个block,需要通过下标索引 DT_REG_ADDR(node_id,idx) DT_REG_SIZE(node_id,idx) 4.读取interrupts属性 获取interrupt specifier数量:DT_NUM_IRQS(node_id) 获取interrupt specifier:通过node id,下标和val来访问中断配置 4.3 遍历宏DeviceTree API都是宏,也就是定义好的常亮,不能再代码中使用循环语句调用(for while),DeviceTree API提供了遍历宏展开宏 . 对设备树中的每一个节点都调用宏函数fn DT_FOREACH_NODE(fn) . 对设备树中每一个status为okay的节点调用宏函数fn DT_FOREACH_STATUS_OKAY_NODE(fn) . 对一个节点的所有子节点遍历调用宏函数fn DT_FOREACH_CHILD(node_id,fn) 更多参考:https://docs.zephyrproject.org/latest/build/dts/api/api.html 4.4 specifier硬件支持 DeviceTree API中还有很多硬件支持的宏,可以直接读取specifier等 参考: https://docs.zephyrproject.org/latest/build/dts/api/api.html https://docs.zephyrproject.org/latest/hardware/index.html 五、Zephyr Driver的实现方式尚未完全理解,后面使用例程的时候理解了再做更新 2023/05/14 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |